namespace ScrumSprintMonitor.UI.Wpf
{
	#region #using Directives

	using System;
	using System.Configuration;
	using System.Diagnostics;
	using System.Globalization;
	using System.IO;
	using System.Reflection;

	using Infrastructure;
	using Infrastructure.Configuration;

	#endregion

	public sealed class Settings
	{
		private static Settings defaultInstance;
		private readonly Configuration appConfig;
		private readonly KeyValueConfigurationCollection settings;

		public Settings()
		{
			var assembly = Assembly.GetEntryAssembly();

			if (assembly == null || DesignerProperties.GetIsInDesignMode(null))
			{
				// Must be in design-mode. Use a mock with the default application settings.

				IsReadOnly = true;

				var appSettings = new AppSettingsSection();
				this.settings = appSettings.Settings;

				foreach (var propertyInfo in GetType().GetProperties())
				{
					var attributes = propertyInfo.GetCustomAttributes(typeof(DefaultSettingValueAttribute), false);
					if (attributes != null && attributes.Length == 1)
					{
						var attribute = (DefaultSettingValueAttribute) attributes[0];

						this.settings.Add(propertyInfo.Name, attribute.Value);
					}
				}
			}
			else
			{
				string binFolder = Path.GetDirectoryName(assembly.Location);
				string scrAssemblyLocation;

				string scrLocation = Path.Combine(binFolder, "ScrumSprintMonitor.Wpf.Application.scr");
				if (File.Exists(scrLocation))
				{
					scrAssemblyLocation = scrLocation;
				}
				else
				{
					string exeLocation = Path.Combine(binFolder, "ScrumSprintMonitor.Wpf.Application.exe");
					scrAssemblyLocation = exeLocation;
				}

				// Open App.Config of executable
				var fileMap = new ExeConfigurationFileMap {
				                                          	ExeConfigFilename = scrAssemblyLocation + ".config"
				                                          };

				if (!File.Exists(fileMap.ExeConfigFilename))
				{
					throw new InvalidOperationException(string.Format("Configuration file is missing at {0}.", fileMap.ExeConfigFilename));
				}

				// Test access level to the configuration file
				try
				{
					using (File.OpenWrite(fileMap.ExeConfigFilename))
					{
						IsReadOnly = false;
					}
				}
				catch (UnauthorizedAccessException)
				{
					IsReadOnly = true;
				}

				this.appConfig = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
				this.settings = this.appConfig.AppSettings.Settings;
			}
		}

		public static Settings Default
		{
			[DebuggerStepThrough]
			get { return defaultInstance ?? (defaultInstance = new Settings()); }
		}

		public bool IsReadOnly { get; private set; }

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("https://tfs08.codeplex.com:443")]
		public string TfsServerName
		{
			[DebuggerStepThrough]
			get { return ReadSetting<string>("TfsServerName"); }
			[DebuggerStepThrough]
			set { WriteSetting("TfsServerName", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue(".\\config\\SprintConfiguration.txt")]
		public string ConfigurationFilePath
		{
			[DebuggerStepThrough]
			get { return ReadSetting<string>("ConfigurationFilePath"); }
			[DebuggerStepThrough]
			set { WriteSetting("ConfigurationFilePath", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue(".\\config\\PresentationConfiguration.txt")]
		public string PresentationConfigurationFilePath
		{
			[DebuggerStepThrough]
			get { return ReadSetting<string>("PresentationConfigurationFilePath"); }
			[DebuggerStepThrough]
			set { WriteSetting("PresentationConfigurationFilePath", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue(".\\config\\team-info.xml")]
		public string TeamMembersConfigurationFilePath
		{
			[DebuggerStepThrough]
			get { return ReadSetting<string>("TeamMembersConfigurationFilePath"); }
			[DebuggerStepThrough]
			set { WriteSetting("TeamMembersConfigurationFilePath", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("True")]
		public bool EnableOutlookIntegration
		{
			[DebuggerStepThrough]
			get { return ReadSetting<bool>("EnableOutlookIntegration"); }
			[DebuggerStepThrough]
			set { WriteSetting("EnableOutlookIntegration", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("False")]
		public bool ConsiderOutlookTentativeAsFreeTime
		{
			[DebuggerStepThrough]
			get { return ReadSetting<bool>("ConsiderOutlookTentativeAsFreeTime"); }
			[DebuggerStepThrough]
			set { WriteSetting("ConsiderOutlookTentativeAsFreeTime", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("True")]
		public bool ReportUnestimatedSprintBacklogItems
		{
			[DebuggerStepThrough]
			get { return ReadSetting<bool>("ReportUnestimatedSprintBacklogItems"); }
			[DebuggerStepThrough]
			set { WriteSetting("ReportUnestimatedSprintBacklogItems", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("Sprinting")]
		public SprintMode ActiveSprintMode
		{
			[DebuggerStepThrough]
			get { return ReadSetting<SprintMode>("ActiveSprintMode"); }
			[DebuggerStepThrough]
			set { WriteSetting("ActiveSprintMode", value); }
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("True")]
		public bool ShowBurndownChart
		{
			[DebuggerStepThrough]
			get { return ReadSetting<bool>("ShowBurndownChart"); }
			[DebuggerStepThrough]
			set { WriteSetting("ShowBurndownChart", value); }
		}

		public void Refresh()
		{
			defaultInstance = null;
		}

		private void GuardWriteAttemptOnReadOnly()
		{
			if (IsReadOnly)
			{
				throw new InvalidOperationException(string.Format("The configuration file is read-only.", this.appConfig.FilePath));
			}
		}

		public void Save()
		{
			GuardWriteAttemptOnReadOnly();

			// Save the changes in App.config file.
			this.appConfig.Save(ConfigurationSaveMode.Modified);

			// Force a reload of a changed section.
			ConfigurationManager.RefreshSection("appSettings");
		}

		public T ReadSetting<T>(string settingName)
		{
			var setting = this.settings[settingName];
			string value;

			if (setting == null)
			{
				var stackFrame = new StackFrame(1);
				var methodBase = stackFrame.GetMethod();
				var propertyInfo = methodBase.DeclaringType.GetProperty(methodBase.Name.Replace("get_", string.Empty));
				Debug.Assert(propertyInfo != null);

				var attr = (DefaultSettingValueAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(DefaultSettingValueAttribute));
				Debug.Assert(attr != null);

				value = attr.Value;
			}
			else
			{
				value = setting.Value;
			}

			Debug.Assert(value != null);

			if (typeof(T).IsEnum)
			{
				return (T) Enum.Parse(typeof(T), value);
			}

			return (T) Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
		}

		public void WriteSetting(string settingName, object value)
		{
			GuardWriteAttemptOnReadOnly();

			var setting = this.settings[settingName];
			string textualValue = Convert.ToString(value, CultureInfo.InvariantCulture);

			if (setting == null)
			{
				this.settings.Add(settingName, textualValue);
			}
			else
			{
				this.settings[settingName].Value = textualValue;
			}
		}
	}
}